package com.tbit.uqbike.protocol.AnalyzeImpl;

import com.tbit.uqbike.protocol.ABaseHandleObj;
import com.tbit.uqbike.protocol.ATerPkg;
import com.tbit.uqbike.tergateway.data.TerGatewayData;
import com.tbit.uqbike.tergateway.entity.AConnInfo;
import com.tbit.uqbike.tergateway.entity.RemoteControl;
import com.tbit.uqbike.tergateway.entity.TerTempData;
import com.tbit.uqbike.tergateway.gt06pkg.AddrQuery;
import com.tbit.uqbike.tergateway.gt06pkg.AlarmInfo;
import com.tbit.uqbike.tergateway.gt06pkg.BmsInfo;
import com.tbit.uqbike.tergateway.gt06pkg.CommPkg;
import com.tbit.uqbike.tergateway.gt06pkg.Login;
import com.tbit.uqbike.tergateway.gt06pkg.MediaInfo;
import com.tbit.uqbike.tergateway.gt06pkg.OrderUpload;
import com.tbit.uqbike.tergateway.gt06pkg.PosInfo;
import com.tbit.uqbike.tergateway.gt06pkg.TerStatus;
import com.tbit.uqbike.util.CharsetName;
import com.tbit.uqbike.util.ConstDefine;
import com.tbit.uqbike.util.ProtocolUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.PooledByteBufAllocator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.TimeZone;

/**
 * @author MyWin E-mail:335918956@qq.com
 * @version 1.0
 * @createTime 2019-6-27 15:51
 */
public class Gt06DeEnCoder extends AProtocol {
    private static Logger logger = LoggerFactory.getLogger(WA205DeEnCoder.class);
    public static final String protocolName = "GT06";

    public static final byte[] PRO_HEAD = new byte[]{(byte) 0x78, (byte) 0x78};
    public static final byte[] PRO_HEAD_EX = new byte[]{(byte) 0x79, (byte) 0x79};
    public static final byte[] PRO_FOOT = new byte[]{(byte) 0xD, (byte) 0xa};
    // 最小报体
    public static final Integer MIN_SIZE = 10;
    public static final Integer MIN_SIZE_EX = 10 + 1;

    // cmd 定义
    public static final byte CMD_LOGIN = 0x01;
    public static final byte CMD_POS = 0x12;
    public static final byte CMD_STATUS = 0x13;
    public static final byte CMD_STRING = 0x15;
    public static final byte CMD_ALARM = 0x16;
    public static final byte CMD_QUERY_ADDR = 0x1A;

    public static final byte CMD_BMS = 0x40;
    public static final byte CMD_MEDIA_DATA = 0x41;

    public static final byte CMD_DOWN_ORDER = (byte) 0x80;


    public static final int INDEX_CMD = 3;

    @Override
    public String getProtocolName() {
        return protocolName;
    }

    @Override
    public List<ATerPkg> analyzeTerPkg(AConnInfo conn, ByteBuf in) {
        LinkedList<ATerPkg> result = new LinkedList<>();
        while (in.readableBytes() >= MIN_SIZE) {
            int inv = in.readerIndex();
            int len, cmd, dataIndex;
            if (in.getByte(inv + 0) == PRO_HEAD[0] && in.getByte(inv + 1) == PRO_HEAD[1]) {
                len = in.getUnsignedByte(inv + 2) + 5;
                cmd = in.getUnsignedByte(inv + INDEX_CMD);
                dataIndex = 4;
            } else if (in.getByte(inv + 0) == PRO_HEAD_EX[0] && in.getByte(inv + 1) == PRO_HEAD_EX[1]) {
                if (in.readableBytes() >= MIN_SIZE_EX) {
                    len = in.getUnsignedShort(inv + 2) + 6;
                    cmd = in.getUnsignedByte(inv + INDEX_CMD + 1);
                    dataIndex = 5;
                } else {
                    break;
                }
            } else {
                in.skipBytes(in.readableBytes());
                break;
            }
            if (in.readableBytes() >= len) {
                // crc校验
                int crcCodeCal = ProtocolUtil.GetCrcByteArray(in, inv + 2, len - 6);
                int crcCodePkg = in.getShort(inv + len - 4);
                if (crcCodeCal != crcCodePkg) {
                    logger.info(String.format("报体解析失败cal_crc:[%d] <> pkg_crc:[%d],crc不匹配", crcCodeCal, crcCodePkg));
                } else {
                    ATerPkg pkg = null;
                    switch (cmd) {
                        case CMD_LOGIN:
                            pkg = analyzeLoginInfo(in, inv, len, cmd, dataIndex);
                            break;
                        case CMD_POS:
                            pkg = analyzePosInfo(in, inv, len, cmd, dataIndex);
                            break;
                        case CMD_STATUS:
                            pkg = analyzeStatusInfo(in, inv, len, cmd, dataIndex);
                            break;
                        case CMD_STRING:
                            pkg = analyzeStringInfo(in, inv, len, cmd, dataIndex);
                            break;
                        case CMD_ALARM:
                            pkg = analyzeAlarmInfo(in, inv, len, cmd, dataIndex);
                            break;
                        case CMD_QUERY_ADDR:
                            pkg = analyzeQueryAddrInfo(in, inv, len, cmd, dataIndex);
                            break;
                        case CMD_BMS:
                            pkg = analyzeBmsInfo(in, inv, len, cmd, dataIndex);
                            break;
                        case CMD_MEDIA_DATA:
                            pkg = analyzeMediaInfo(in, inv, len, cmd, dataIndex);
                            break;
                        default:
                            logger.info(String.format("报体解析失败cmd:[%d],未知命令字", cmd));
                            break;
                    }
                    if (pkg != null) {
                        pkg.setConnId(conn.connId);
                        pkg.setProName(getProtocolName());
                        result.add(pkg);
                        if (pkg.autoRsp()) {
                            final ByteBuf rspData = pkg.getRsp(conn, this);
                            if (null != rspData && rspData.readableBytes() > 0) {
                                conn.downMsg(rspData);
                            }
                        }
                        byte[] signPkg = new byte[len];
                        in.getBytes(in.readerIndex(), signPkg);
                        pkg.signPkg = signPkg;
                    }
                }
                in.skipBytes(len);
            } else {
                break;
            }
        }

        return result;
    }

    public ByteBuf builtRemoteControlPkg(String order, int serNO) {
        ByteBuf byteBuf = PooledByteBufAllocator.DEFAULT.directBuffer();
        byteBuf.writeBytes(PRO_HEAD);
        try {
            byte[] orderbs = order.getBytes(CharsetName.US_ASCII);
            byteBuf.writeByte(orderbs.length + 4 + 1 + 2 + 1 + 4);
            byteBuf.writeByte(CMD_DOWN_ORDER);
            byteBuf.writeByte(orderbs.length + 4);
            byteBuf.writeInt(serNO);
            byteBuf.writeBytes(orderbs);
            byteBuf.writeShort(0);
            byteBuf.writeShort(0);
            writeCrc(byteBuf);
            byteBuf.writeBytes(PRO_FOOT);
            return byteBuf;
        } catch (UnsupportedEncodingException e) {
            return null;
        }
    }

    @Override
    public ByteBuf builtRemoteControlPkg(AConnInfo conn, ABaseHandleObj aBaseHandleObj) {
        ByteBuf byteBuf = null;
        RemoteControl remoteControl = (RemoteControl) aBaseHandleObj;
        try {
            String order = null;
            if (ConstDefine.CONTROL_TYPE_CONTROL.equals(remoteControl.controlType)) {
                Integer code = Integer.parseInt(remoteControl.paramName);
                if (code == 0x1) {
                    // 还车
                    order = "DYD#";
                } else if (code == 0xb) {
                    // 借车
                    order = "HFYD#";
                } else if(code == 0xf) {
                    // 开电池锁
                    order = "BATLOCK#0";
                } else if(code == 0x10) {
                    // 关电池锁
                    order = "BATLOCK#1";
                }
            } else if (Objects.equals(ConstDefine.CONTROL_TYPE_GET, remoteControl.controlType)) {
                order = String.format("READPARAM#%s", remoteControl.paramName);
            } else if (Objects.equals(ConstDefine.CONTROL_TYPE_SET, remoteControl.controlType)) {
                order = String.format("WRITEPARAM#%s", remoteControl.paramName);
            }
            if (null != order) {
                TerTempData terTempData = TerGatewayData.getTerTempDataByMno(remoteControl.sn);
                int serNo = terTempData.getValidSerNo(aBaseHandleObj);
                return builtRemoteControlPkg(order, serNo);
            }
        } catch (Exception e) {
            if (byteBuf != null) {
                byteBuf.clear();
            }
            logger.error("构造下行数据包异常", e);
        }
        return null;
    }

    public static void initCommPkg(CommPkg pkg, ByteBuf in, int inv, int len, int cmd, int dataIndex) {
        int i = 0;
        in.getBytes(inv + i, pkg.startCode);
        i += 2;
        if (dataIndex == 5) {
            pkg.pkgLen = in.getUnsignedShort(inv + i);
            i += 2;
        } else {
            pkg.pkgLen = in.getUnsignedByte(inv + i);
            i += 1;
        }
        pkg.cmd = cmd;
        i += 1;
        pkg.serNo = in.getUnsignedShort(inv + len - 6);
        pkg.crcCode = in.getUnsignedShort(inv + len - 4);
        in.getBytes(inv + len - 2, pkg.stopCode);
    }

    public static ByteBuf buildCommRsp(CommPkg pkg) {
        ByteBuf byteBuf = PooledByteBufAllocator.DEFAULT.directBuffer();
        byteBuf.writeBytes(pkg.startCode);
        if (pkg.startCode[0] == PRO_HEAD[0]) {
            byteBuf.writeByte(5);
        } else {
            byteBuf.writeShort(6);
        }
        byteBuf.writeByte(pkg.cmd);
        byteBuf.writeShort(pkg.serNo);
        writeCrc(byteBuf);
        byteBuf.writeBytes(pkg.stopCode);
        return byteBuf;
    }

    public static void writeCrc(ByteBuf byteBuf) {
        int crcCodeCal = ProtocolUtil.GetCrcByteArray(byteBuf, byteBuf.readerIndex() + 2, byteBuf.readableBytes() - 2);
        byteBuf.writeShort(crcCodeCal);
    }

    public static Date getTime(ByteBuf byteBuf, int inv) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        sdf.setTimeZone(TimeZone.getTimeZone("GMT+:00:00"));
        String dtStr = String.format("%d-%d-%d %d:%d:%d",
                2000 + byteBuf.getUnsignedByte(inv + 0), byteBuf.getUnsignedByte(inv + 1),
                byteBuf.getUnsignedByte(inv + 2), byteBuf.getUnsignedByte(inv + 3),
                byteBuf.getUnsignedByte(inv + 4), byteBuf.getUnsignedByte(inv + 5));
        try {
            return sdf.parse(dtStr);
        } catch (ParseException e) {
            e.printStackTrace();
            return new Date(System.currentTimeMillis());
        }
    }

    public static Date getTimeInt(ByteBuf byteBuf, int inv) {
        return ProtocolUtil.BinGetTime(byteBuf, inv);
    }

    public ATerPkg analyzeLoginInfo(ByteBuf in, int inv, int len, int cmd, int dataIndex) {
        Login pkg = new Login();
        initCommPkg(pkg, in, inv, len, cmd, dataIndex);
        int i = dataIndex;
        pkg.mno = ProtocolUtil.BinGetBcd(in, inv + i, 8).substring(1);
        i += 8;
        return pkg;
    }

    public ATerPkg analyzePosInfo(ByteBuf in, int inv, int len, int cmd, int dataIndex) {
        PosInfo pkg = new PosInfo();
        initCommPkg(pkg, in, inv, len, cmd, dataIndex);
        int i = dataIndex;
        pkg.posDt = getTime(in, inv + i);
        i += 6;
        pkg.gpsCnt = in.getUnsignedByte(inv + i);
        i += 1;
        long lat = in.getUnsignedInt(inv + i);
        i += 4;
        long lon = in.getUnsignedInt(inv + i);
        i += 4;
        pkg.speed = in.getUnsignedByte(inv + i);
        i += 1;
        pkg.status = in.getUnsignedShort(inv + i);
        i += 2;
        pkg.cellId = String.format("%d.%d.%d.%d",
                in.getUnsignedShort(inv + i + 0), in.getUnsignedByte(inv + i + 2),
                in.getUnsignedShort(inv + i + 3), in.getUnsignedMedium(inv + i + 5));
        i += 8;
        if ((pkg.status & 0x800) == 0) {
            pkg.lon = (lon * 1.0) / (30000 * 60);
        } else {
            pkg.lon = -(lon * 1.0) / (30000 * 60);
        }
        if ((pkg.status & 0x400) == 0) {
            pkg.lat = (lat * 1.0) / (30000 * 60);
        } else {
            pkg.lat = (lat * 1.0) / (30000 * 60);
        }
        pkg.dir = pkg.status & 0x3ff;
        pkg.pointType = ((pkg.status & 0x1000) != 0) ? 1 : 0;
        return pkg;
    }

    public ATerPkg analyzeStatusInfo(ByteBuf in, int inv, int len, int cmd, int dataIndex) {
        TerStatus pkg = new TerStatus();
        initCommPkg(pkg, in, inv, len, cmd, dataIndex);
        int i = dataIndex;
        // 状态信息
        pkg.terStatus = in.getUnsignedByte(inv + i);
        i += 1;
        pkg.power = in.getUnsignedByte(inv + i);
        i += 1;
        pkg.gsmLevel = in.getUnsignedByte(inv + i);
        i += 1;
        pkg.alarmCode = in.getUnsignedByte(inv + i);
        i += 1;
        pkg.lang = in.getUnsignedByte(inv + i);
        i += 1;
        return pkg;
    }

    public ATerPkg analyzeStringInfo(ByteBuf in, int inv, int len, int cmd, int dataIndex) {
        OrderUpload pkg = new OrderUpload();
        initCommPkg(pkg, in, inv, len, cmd, dataIndex);
        int i = dataIndex;
        pkg.orderLen = in.getUnsignedByte(inv + i);
        i += 1;
        pkg.serNo = in.getInt(inv + i);
        i += 4;
        pkg.orderContent = in.getCharSequence(inv + i, pkg.orderLen - 4, StandardCharsets.US_ASCII).toString();
        i += (pkg.orderLen - 4);
        pkg.lang = in.getUnsignedShort(inv + i);
        return pkg;
    }

    public ATerPkg analyzeAlarmInfo(ByteBuf in, int inv, int len, int cmd, int dataIndex) {
        AlarmInfo pkg = new AlarmInfo();
        initCommPkg(pkg, in, inv, len, cmd, dataIndex);
        int i = dataIndex;
        pkg.posDt = getTime(in, inv + i);
        i += 6;
        pkg.gpsCnt = in.getUnsignedByte(inv + i);
        i += 1;
        long lat = in.getUnsignedInt(inv + i);
        i += 4;
        long lon = in.getUnsignedInt(inv + i);
        i += 4;
        pkg.speed = in.getUnsignedByte(inv + i);
        i += 1;
        pkg.status = in.getUnsignedShort(inv + i);
        i += 2;
        i++;//跳过基站信息长度字段
        pkg.cellId = String.format("%d.%d.%d.%d",
                in.getUnsignedShort(inv + i + 0), in.getUnsignedByte(inv + i + 2),
                in.getUnsignedShort(inv + i + 3), in.getUnsignedMedium(inv + i + 5));
        i += 8;
        if ((pkg.status & 0x800) == 0) {
            pkg.lon = (lon * 1.0) / (30000 * 60);
        } else {
            pkg.lon = -(lon * 1.0) / (30000 * 60);
        }
        if ((pkg.status & 0x400) == 0) {
            pkg.lat = (lat * 1.0) / (30000 * 60);
        } else {
            pkg.lat = (lat * 1.0) / (30000 * 60);
        }
        pkg.dir = pkg.status & 0x3ff;
        pkg.pointType = ((pkg.status & 0x1000) != 0) ? 1 : 0;
        // 状态信息
        pkg.terStatus = in.getUnsignedByte(inv + i);
        i += 1;
        pkg.power = in.getUnsignedByte(inv + i);
        i += 1;
        pkg.gsmLevel = in.getUnsignedByte(inv + i);
        i += 1;
        pkg.alarmCode = in.getUnsignedByte(inv + i);
        i += 1;
        pkg.lang = in.getUnsignedByte(inv + i);
        i += 1;
        return pkg;
    }

    public ATerPkg analyzeQueryAddrInfo(ByteBuf in, int inv, int len, int cmd, int dataIndex) {
        AddrQuery pkg = new AddrQuery();
        initCommPkg(pkg, in, inv, len, cmd, dataIndex);
        int i = dataIndex;
        pkg.posDt = getTime(in, inv + i);
        i += 6;
        pkg.gpsCnt = in.getUnsignedByte(inv + i);
        i += 1;
        long lat = in.getUnsignedInt(inv + i);
        i += 4;
        long lon = in.getUnsignedInt(inv + i);
        i += 4;
        pkg.speed = in.getUnsignedByte(inv + i);
        i += 1;
        pkg.status = in.getUnsignedShort(inv + i);
        i += 2;

        if ((pkg.status & 0x800) == 0) {
            pkg.lon = (lon * 1.0) / (30000 * 60);
        } else {
            pkg.lon = -(lon * 1.0) / (30000 * 60);
        }
        if ((pkg.status & 0x400) == 0) {
            pkg.lat = (lat * 1.0) / (30000 * 60);
        } else {
            pkg.lat = (lat * 1.0) / (30000 * 60);
        }
        pkg.dir = pkg.status & 0x3ff;
        pkg.pointType = ((pkg.status & 0x1000) != 0) ? 1 : 0;
        return pkg;
    }

    public ATerPkg analyzeBmsInfo(ByteBuf in, int inv, int len, int cmd, int dataIndex) {
        BmsInfo pkg = new BmsInfo();
        initCommPkg(pkg, in, inv, len, cmd, dataIndex);
        int i = dataIndex;
        pkg.mno = ProtocolUtil.BinGetBcd(in, inv + i, 8).substring(1);
        i += 8;
        pkg.dt = getTimeInt(in, inv + i);
        i += 4;
        pkg.soc = in.getUnsignedByte(inv + i);
        i += 1;
        //跳过2字节可用容量
        pkg.batteryRemaining = in.getUnsignedShort(inv + i);
        i += 2;
        // 跳过1字节绝对soc
        pkg.batteryRelativelyRemaining = in.getUnsignedByte(inv + i);
        i += 1;
        // 跳过2字节绝对满电容量
        pkg.batteryElectricity = in.getUnsignedShort(inv + i);
        i += 2;
        pkg.soh = in.getUnsignedByte(inv + i);
        i += 1;
        pkg.batteryTemp = in.getUnsignedShort(inv + i) + ConstDefine.TEMP_KELVINS;
        i += 2;
        pkg.batteryEI = in.getUnsignedShort(inv + i);
        i += 2;
        pkg.batteryEU = in.getUnsignedShort(inv + i);
        i += 2;
        pkg.dischargeCnt = in.getUnsignedShort(inv + i);
        i += 2;
        // 14字节 1～7 节电池电压
        StringBuilder sb = new StringBuilder();
        for (int ii = 0; ii < 7; ii++) {
            sb.append(String.format("%d,", in.getUnsignedShort(inv + i + ii * 2)));
        }
        pkg.power1_7 = sb.toString();
        i += 14;
        // 14字节 8～14 节电池电压
        sb = new StringBuilder();
        for (int ii = 0; ii < 7; ii++) {
            sb.append(String.format("%d,", in.getUnsignedShort(inv + i + ii * 2)));
        }
        pkg.power8_14 = sb.toString();
        i += 14;
        //  2字节 当前充电间隔时间
        pkg.currChargeInv = in.getUnsignedShort(inv + i);
        i += 2;
        //  2字节 最大充电间隔时间
        pkg.maxChargeInv = in.getUnsignedShort(inv + i);
        i += 2;
        // 16字节 读写成本条形码
        pkg.codeStr = ProtocolUtil.BinGetAsciiStringTrimZero(in, inv + i, 16);
        i += 16;
        // 2字节 读版本号
        pkg.batteryVer = String.format("%d,%d", in.getUnsignedByte(inv + i), in.getUnsignedByte(inv + i + 1));
        i += 2;
        // 16字节 电池组制造厂名称
        pkg.factoryName = ProtocolUtil.BinGetAsciiStringTrimZero(in, inv + i, 16);
        i += 16;
        pkg.batteryStatus = ProtocolUtil.BinGetBcd(in, inv + i, 4);
        ;
        i += 4;
        return pkg;
    }

    public ATerPkg analyzeMediaInfo(ByteBuf in, int inv, int len, int cmd, int dataIndex) {
        MediaInfo pkg = new MediaInfo();
        initCommPkg(pkg, in, inv, len, cmd, dataIndex);
        int i = dataIndex;
        pkg.mno = ProtocolUtil.BinGetBcd(in, inv + i, 8).substring(1);
        i += 8;
        return pkg;
    }
}
